using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace cssp
{

	/// <summary>
	/// An reference to a symbol to be replaced with the associated symbol's value.
	/// </summary>
	public class SymbolReference
	{
		public string Name;
		public int Offset;
	}

	public class CssxParser
	{
		ICssxScanner mScanner;
		Unit mUnit;
		CodeBlock mCurrentTarget;

		public delegate void TokenDelegate(string[] tokens);
		public TokenDelegate TokenHandler;

		public delegate bool ErrorDelegate(string message);
		public ErrorDelegate ErrorHandler;

		public ICssxScanner Scanner
		{
			get { return mScanner; }
			set { mScanner = value; }
		}

		public CssxParser()
		{
		}

		public Unit Parse(string filename, TextReader reader)
		{
			if (string.IsNullOrEmpty(filename))
				throw new ArgumentNullException("filename");

			if (reader == null)
				throw new ArgumentNullException("reader");

			if (mScanner == null)
				throw new InvalidOperationException("Scanner property must be set before calling Parse.");

			mScanner.OnToken = ParseTokens;
			mCurrentTarget = null;

			Log.WriteLine(LogLevel.Info, "Parsing " + Path.GetFileName(filename) + ":");

			mUnit = new Unit(filename);
			mUnit.Text = reader.ReadToEnd();
			using (StringReader r = new StringReader(mUnit.Text))
			{
				mScanner.Scan(r);
			}

			if (mCurrentTarget != null)
				OnError("target " + mCurrentTarget.Name + ", at line " + mCurrentTarget.StartLineNum + " is not closed.");

			return mUnit;
		}

		void ParseTokens(Token[] tokens)
		{
			switch (tokens[0].Value)
			{
				case "import":
					ParseImport(tokens);
					break;
				case "define":
					ParseDefine(tokens);
					break;
				case "target":
					ParseTarget(tokens);
					break;
				case "endtarget":
					ParseEndTarget(tokens);
					break;
				default:
					// Mark symbol references
					foreach (Token t in tokens)
					{
						if (Regex.IsMatch(t.Value, @"\$[\w]+"))
							OnSymbolReference(t);
					}
					break;
			}
		}

		#region keyword parsers

		void OnSymbolReference(Token token)
		{
			mUnit.SymbolReferences.Add(token);
		}

		void ParseImport(Token[] tokens)
		{
			if (tokens.Length < 2)
			{
				OnError("import has too few arguments.");
				return;
			}

			CodeBlock import = new CodeBlock(BlockType.Import);
			import.StartLineNum = import.EndLineNum = tokens[0].Row;
			import.Name = tokens[1].Value;
			mUnit.Imports.Add(import);
		}

		void ParseDefine(Token[] tokens)
		{
			if (tokens.Length < 3)
			{
				OnError(tokens[0].Value + " has too few arguments.");
				return;
			}

			string symbolName = tokens[1].Value;
			string symbolValue = tokens[2].Value;

			if (mUnit.Symbols.Find(delegate(Symbol s) { return s.Name == symbolName; }) != null)
			{
				OnError(symbolName + " is already defined in this file.");
				return;
			}

			if (Regex.IsMatch(symbolValue, @"\$" + symbolName + @"\b"))
			{
				OnError("Recursive symbol definition: " + symbolName);
				return;
			}

			Symbol symbol = new Symbol();
			symbol.StartLineNum = symbol.EndLineNum = tokens[0].Row;
			symbol.Name = symbolName;
			symbol.Value = symbolValue;
			mUnit.Symbols.Add(symbol);
		}

		void ParseTarget(Token[] tokens)
		{
			if (mCurrentTarget != null)
			{
				OnError("targets cannot be nested.");
				return;
			}

			if (tokens.Length < 2)
			{
				OnError("target has too few arguments.");
				return;
			}

			string targetName = tokens[1].Value;
			if (!Regex.IsMatch(targetName, @"!?[\w]+"))
			{
				OnError("target name is not valid: " + targetName);
				return;
			}

			CodeBlock target = new CodeBlock(BlockType.Target);
			target.StartLineNum = tokens[0].Row;
			target.Name = targetName;
			if (!mUnit.Targets.ContainsKey(targetName))
				mUnit.Targets.Add(targetName, new List<CodeBlock>());
			mUnit.Targets[targetName].Add(target);

			mCurrentTarget = target;
		}

		void ParseEndTarget(Token[] tokens)
		{
			if (mCurrentTarget == null)
			{
				OnError("endtarget found when no target block is open.");
				return;
			}

			mCurrentTarget.EndLineNum = tokens[0].Row;
			mCurrentTarget = null;
		}

		#endregion

		bool IsValidIdentifier(string name)
		{
			return Regex.IsMatch(name, @"^\w+$");
		}

		void OnError(string message)
		{
			mUnit.HasErrors = true;
			string msg = Path.GetFileName(mUnit.Filename) + "(" + mScanner.LineNumber + "): " + message;
			Log.WriteLine(LogLevel.Error, msg);
		}
	}

	
}
